package com.zhangsun.springShiro.framework.shiro;

import cn.hutool.core.collection.CollUtil;
import com.zhangsun.springShiro.common.provider.RedisProvider;
import com.zhangsun.springShiro.framework.jwt.JwtToken;
import com.zhangsun.springShiro.framework.jwt.JwtTokenUtil;
import com.zhangsun.springShiro.system.bean.TheUser;
import com.zhangsun.springShiro.system.entity.ResourceEntity;
import com.zhangsun.springShiro.system.entity.RoleEntity;
import com.zhangsun.springShiro.system.entity.UserEntity;
import com.zhangsun.springShiro.system.mapper.RoleMapper;
import com.zhangsun.springShiro.system.service.ResourceService;
import com.zhangsun.springShiro.system.service.RoleService;
import com.zhangsun.springShiro.system.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private ResourceService resourceService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        TheUser theUser = (TheUser) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        // 角色
        List<RoleEntity> roleEntities = roleService.findRolesByUser(theUser.getUserId());
        authorizationInfo.setRoles(getRoles(roleEntities));

        // 权限
        List<ResourceEntity> resourceEntities = resourceService.findResourcesByRoles(roleEntities, theUser.getUserId());
        authorizationInfo.setStringPermissions(getPermissions(resourceEntities));
        return authorizationInfo;
    }

    private Set<String> getRoles(List<RoleEntity> roleEntities) {
        Set<String> roles = new HashSet<>();
        if (CollUtil.isNotEmpty(roleEntities)) {
            roleEntities.forEach(r -> roles.add(r.getRole()));
        }
        return roles;
    }

    private Set<String> getPermissions(List<ResourceEntity> resourceEntities) {
        Set<String> permissions = new HashSet<>();
        if (CollUtil.isNotEmpty(resourceEntities)) {
            resourceEntities.forEach(r -> permissions.add(r.getPermission()));
        }
        return permissions;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String token = (String) authenticationToken.getCredentials();
        // 判断redis中token是否已失效
        if (!jwtTokenUtil.validateRedisToken(token)) {
            jwtTokenUtil.del(token);
            throw new AuthenticationException("签名已失效，请重新登陆");
        }

        // 解密获得username，用于和数据库进行对比
        String username = jwtTokenUtil.getUsernameFromToken(token);
        if (username == null) {
            jwtTokenUtil.del(token);
            throw new AuthenticationException("签名无效，请重新登陆");
        }
        //通过username从数据库中查找 SysUser对象
        //实际项目中，这里可以根据实际情况做缓存，如果不做，Shiro自己也是有时间间隔机制，2分钟内不会重复执行该方法
        UserEntity userInfo = userService.findByUserNo(username);
        if (userInfo == null) {
            jwtTokenUtil.del(token);
            throw new AuthenticationException("用户不存在");
        }
        // 判断用户状态
        if (userInfo.getDisabled()) {
            throw new AuthenticationException("账号已被锁定,请联系管理员!");
        }
        // 验证token
        if (!jwtTokenUtil.validateToken(token, userInfo.getUserNo(), userInfo.getPassword())) {
            jwtTokenUtil.del(token);
            throw new AuthenticationException("签名已失效，请重新登录!");
        }

        TheUser theUser = new TheUser();
        theUser.setUserId(userInfo.getId());
        return new SimpleAuthenticationInfo(theUser, token, getName());
    }
}
